package utils

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"
)

type TokenManager struct {
	rwLock     sync.RWMutex
	expireTime map[string]time.Time // map from token to expire time
	used       int                  // how many times this token manager has been used
}

func NewTokenManager() *TokenManager {
	return &TokenManager{
		expireTime: make(map[string]time.Time),
		used:       0,
	}
}

var (
	tokenManager *TokenManager
	once         sync.Once
)

func GetTokenManager() *TokenManager {
	once.Do(func() {
		tokenManager = NewTokenManager()
	})

	return tokenManager
}

func (t *TokenManager) GC() {
	t.rwLock.Lock()
	defer t.rwLock.Unlock()
	t.used++
	if t.used%1000 == 0 {
		for k, v := range t.expireTime {
			if v.Before(time.Now()) {
				delete(t.expireTime, k)
			}
		}
	}
}

func (t *TokenManager) GenerateToken(user_id uint, user_type string) (string, error) {
	t.GC()
	n_seconds, err := strconv.Atoi(os.Getenv("EXPIRE_SECONDS"))

	if err != nil {
		return "", err
	}

	claims := jwt.MapClaims{}
	claims["authorized"] = true
	claims["user_id"] = user_id
	claims["user_type"] = user_type
	expire := time.Now().Add(time.Second * time.Duration(n_seconds))
	claims["exp"] = expire.Unix()
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	signedString, err := token.SignedString([]byte(os.Getenv("API_SECRET")))
	if err != nil {
		return "", err
	}

	t.rwLock.Lock()
	t.expireTime[signedString] = expire
	t.rwLock.Unlock()
	return signedString, nil
}

func ExtractToken(c *gin.Context) string {
	token := c.Query("token")
	if token != "" {
		return token
	}
	bearerToken := c.Request.Header.Get("Authorization")
	if len(strings.Split(bearerToken, " ")) == 2 {
		return strings.Split(bearerToken, " ")[1]
	}
	return ""
}

func (t *TokenManager) ExtractUserInfo(c *gin.Context) (map[string]any, error) {
	t.GC()
	tokenString := ExtractToken(c)
	t.rwLock.RLock()
	if _, ok := t.expireTime[tokenString]; !ok {
		t.rwLock.RUnlock()
		return nil, fmt.Errorf("token not found")
	}
	t.rwLock.RUnlock()

	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}
		return []byte(os.Getenv("API_SECRET")), nil
	})
	if err != nil {
		return nil, err
	}
	claims, ok := token.Claims.(jwt.MapClaims)
	if ok && token.Valid {
		return map[string]any(claims), nil
	}
	return nil, fmt.Errorf("token not valid")
}

func (t *TokenManager) DeleteToken(token string) {
	t.GC()
	t.rwLock.Lock()
	defer t.rwLock.Unlock()
	delete(t.expireTime, token)
}
